﻿// Copyright (c) 2010 Thomas Stuefe
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Xunit;
using zencat.sqlite;

using System.IO;
using System.Diagnostics;

namespace UnitTests
{
    /// <summary>
    /// This class provides base services for all tests:
    /// - Initialization/Cleanup of sqlite library
    /// - Opening a database, 
    /// </summary>
    public class SqliteTestBase : IDisposable
    {
        // UP this for new sqlite versions
        const string _expectedVersionString = "3.7.7.1";
        const int _expectedVersionNumber = 3007007;

        private static string _rootDirectory = null;

        public static string RootDirectory {
            get { return _rootDirectory; }
        }

        static SqliteTestBase() {
            _rootDirectory =
                Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "unittests_zencat_sqlite");
            if (!Directory.Exists(_rootDirectory)) {
                Directory.CreateDirectory(_rootDirectory);
            }
            Assert.True(Directory.Exists(_rootDirectory));
        }

        private string _testDirectory = null;

        protected String TestDirectory {
            get { return _testDirectory; }
        }

        private string _testName = null;

        protected string TestName {
            get { return _testName; }
        }

        public SqliteTestBase() {

            Assert.Equal(Sqlite.SqliteVersionNumber, _expectedVersionNumber);
            Assert.Equal(Sqlite.SqliteVersionString, _expectedVersionString);

            _testName = this.GetType().FullName;

            // create a fresh directory for this test, while letting the old test dirs live
            for (int i = 0; i < 1000; i++) {
                string testDir = Path.Combine(RootDirectory, TestName + "_" + i);
                if (!Directory.Exists(testDir)) {
                    Directory.CreateDirectory(testDir);
                    _testDirectory = testDir;
                    break;
                }
            }
            Assert.NotNull(_testDirectory);

            string traceFileName =
                Path.Combine(_testDirectory, "trace.log");

            Sqlite.TraceFileName = traceFileName;
            Sqlite.TraceDestination = TrcDestination.TraceToFile;
            Sqlite.TraceLevel = TrcLevel.Verbose;

            Assert.Equal(Sqlite.TraceLevel, TrcLevel.Verbose);
            Assert.Equal(Sqlite.TraceDestination, TrcDestination.TraceToFile);
            Assert.Equal(Sqlite.TraceFileName, traceFileName);

            Sqlite.TraceLine(">>> " + TestName);
            Assert.True(File.Exists(traceFileName));

            Sqlite.Initialize();
            Assert.True(Sqlite.Initialized);

        }

        public void Dispose() {
            Assert.True(Sqlite.Initialized);
            Sqlite.Shutdown();
            Assert.False(Sqlite.Initialized);

            Sqlite.TraceLine("<<< " + TestName);

            Sqlite.TraceLevel = TrcLevel.Off;
            Assert.Equal(Sqlite.TraceLevel, TrcLevel.Off);

        }


        protected IDatabase CreateAndCheckTestDatabase(int index, bool withSchema) {
            IDatabase db = null;
            lock (this) {
                for (int i = 0; i < 1000 && db == null; i++) {
                    string fname = Path.Combine(TestDirectory, "testdb_" + index + ".database");
                    if (!File.Exists(fname)) {
                        db = Sqlite.OpenDatabase(fname);
                        Assert.NotNull(db);
                        Assert.Equal(db.DatabaseFile, fname);
                        Assert.True(db.WasCreated);
                        Assert.True(db.NumberOfChangedRows == 0);
                        Assert.True(db.LastInsertRowId == 0);
                        Assert.True(db.LastErrorCode == ErrorCode.EC_SQLITE_OK);
                        Assert.False(db.InTransaction);
                    }
                }
            }
            Assert.NotNull(db);
            if (withSchema) {
                db.ExecuteSql("CREATE TABLE t1 ( " +
                    "id INTEGER PRIMARY KEY, " +
                    "i1 INTEGER, t1 TEXT); ");
                db.ExecuteSql("CREATE TABLE t2 ( " +
                    "id INTEGER PRIMARY KEY, " +
                    "i1 INTEGER, t1 TEXT); ");
            }
            return db;
        }

        protected IStatement CreateAndCheckNonQueryStatement(
            IDatabase db, string sql, int expectedParamCount
            ) 
        {
            IStatement stmt = db.CreateStatement(sql);
            Assert.NotNull(stmt);
            Assert.True(db.LastErrorCode == ErrorCode.EC_SQLITE_OK);
            Assert.Same(stmt.Database, db);
            Assert.False(stmt.HasData);
            Assert.True(stmt.IsOpen);
            Assert.True(stmt.ParameterCount == expectedParamCount);
            Assert.True(stmt.ColumnNames.Length == 0);
            Assert.True(stmt.ColumnTypes.Length == 0);
            Assert.True(stmt.ColumnCount == 0);
            Assert.Equal(stmt.Sql, sql);
            return stmt;
        }

        protected IStatement CreateAndCheckQueryStatement(
            IDatabase db, string sql, int expectedParamCount,
            string[] expectedColumnNames
            ) 
        {
            IStatement stmt = db.CreateStatement(sql);
            Assert.NotNull(stmt);
            Assert.True(db.LastErrorCode == ErrorCode.EC_SQLITE_OK);
            Assert.Same(stmt.Database, db);
            Assert.False(stmt.HasData);
            Assert.True(stmt.IsOpen);
            Assert.True(stmt.ParameterCount == expectedParamCount);
            Assert.True(stmt.ColumnNames.Length == stmt.ColumnCount);
            Assert.True(stmt.ColumnNames.SequenceEqual(expectedColumnNames));
            for (int i = 0; i < stmt.ColumnCount; i++) {
                Assert.Equal(stmt.ColumnNames[i], stmt.GetColumnName(i));
            }
            Assert.Equal(stmt.Sql, sql);

            return stmt;
        }

        // helper function, checks that a statement has expected return types
        protected void CheckExpectedResultTypes(IStatement stmt, DataType[] expectedTypes) {
            Assert.True(stmt.ColumnTypes.SequenceEqual(expectedTypes));
            Assert.Equal(stmt.ColumnCount, stmt.ColumnTypes.Length);
            for (int i = 0; i < stmt.ColumnCount; i++) {
                Assert.True(stmt.GetColumnType(i) == expectedTypes[i]);
            }
        }

        // returns a statement which returns the highest value of column "id" in table 
        protected IStatement CreateAndCheckMaxIdStatement(
            IDatabase db, string table
            ) {

            string sql = 
                "SELECT MAX (id) AS maxval from " + table + ";";

            IStatement stmt = db.CreateStatement(sql);

            Assert.NotNull(stmt);

            Assert.True(db.LastErrorCode == ErrorCode.EC_SQLITE_OK);
            Assert.Same(stmt.Database, db);
            Assert.False(stmt.HasData);
            Assert.True(stmt.IsOpen);
            Assert.True(stmt.ParameterCount == 0);

            Assert.True(stmt.ColumnNames.SequenceEqual(new string[] { "maxval" }));

            Assert.Equal(stmt.Sql, sql);
            return stmt;
        }

        protected int GetHighestIdInTable(IDatabase db, string table) {
            IStatement stmt = CreateAndCheckMaxIdStatement(db, table);
            Assert.NotNull(stmt);
            stmt.Step();
            Assert.True(stmt.HasData);
            int result = (int)stmt.ColumnValueInt64(0);
            stmt.Close();
            return result;
        }

        protected IStatement CreateAndCheckCountStatment(
            IDatabase db, string table, string whereCondition
            ) 
        {
            string sql = whereCondition == null ?
                "SELECT COUNT (*) AS CNT from " + table + ";" :
                "SELECT COUNT (*) AS CNT from " + table + " " + whereCondition + ";";

            IStatement stmt = db.CreateStatement(sql);

            Assert.NotNull(stmt);

            Assert.True(db.LastErrorCode == ErrorCode.EC_SQLITE_OK);
            Assert.Same(stmt.Database, db);
            Assert.False(stmt.HasData);
            Assert.True(stmt.IsOpen);
            Assert.True(stmt.ParameterCount == 0);

            Assert.True(stmt.ColumnNames.SequenceEqual( new string[] { "CNT" } ));
 
            Assert.Equal(stmt.Sql, sql);
            return stmt;        
        }

        protected int CountRowsInTable(IDatabase db, string table, string whereCondition) {
            IStatement stmt = CreateAndCheckCountStatment(db, table, whereCondition);
            Assert.NotNull(stmt);
            stmt.Step();
            Assert.True(stmt.HasData);
            int result = (int) stmt.ColumnValueInt64(0);
            stmt.Close();
            return result;
        }

        protected void CloseAndCheckQueryStatement(IStatement stmt) {

            Assert.True(stmt.IsOpen);
            Assert.NotNull(stmt.Sql);
            stmt.Close();
            Assert.False(stmt.IsOpen);
            Assert.Null(stmt.Sql);

            // try accessing it, should result in an sql exception
            Assert.Throws<SqliteException>(
                delegate {
                    stmt.Reset();
                }
                );
        }

    }
}
